Table of contents

  • Imports
  • Task
  • Load
  • Courses and groups info
  • EDA
    • Результаты курсов
      • Гистограммы
      • Бокс-плоты
  • Воронки

Imports¶

In [ ]:
import pandas as pd
import numpy as np
import itertools

import matplotlib.pyplot as plt
import seaborn as sns
import matplotlib.ticker as ticker

from plotly import graph_objects as go
import plotly.io as pio

pio.renderers.default = "notebook_connected"

pio.templates["letovo"] = go.layout.Template(
    layout_colorway=['#0D3174', '#FDC300', '#A40C30', '#00ADB9',
                     '#B2B2B2']
)
pio.templates.default = "letovo"

import warnings
warnings.filterwarnings("ignore")

plt.rcParams['figure.figsize'] = (16,10)
plt.rcParams['font.size'] = 22

sns.set_style('darkgrid')

plt.rcParams['figure.figsize'] = (16,9)
plt.rcParams['font.size'] = 22

YELLOW = '#FDC300'
BLUE = '#0D3174'
palette_dict = {1: YELLOW, 0: 'grey'}

Task¶

Когорты обучения в курсах: ID курса, название, даты, ID теста

  1. Конверсия: заявки/одобрены/приступили
  2. Результаты отборочного теста
  3. Средний результат (% от макс баллов)
  4. УП закончили больше 50%
  5. УП закончили 100%
  6. Абитуриенты (кол-во и %) от приступивших к курсу
  7. Абитуриенты в рейтинге результатов : % от макс кол-ва баллов

Load¶

Выгружаем данные по одобренным заявкам

In [ ]:
df = pd.read_csv('students_course_resullts.csv', parse_dates=['course_start_at', 'accepted_at', 'created_at'])
display(df.head())
df.info()
lo_id grade study_year_id school_app course_id course_name created_at accepted_at group_name lessons_completed course_start_at course_lesson_cnt student_score max_score test_result_final
0 523390 6 2 1 7 Биологический калейдоскоп 2022-09-08 13:52:34 2022-09-26 23:59:59 Биолог. калейдоскоп 26.09 9 2022-10-16 18:54:41 11 142 249 100.0
1 525181 6 2 1 7 Биологический калейдоскоп 2022-09-05 19:41:42 2022-09-26 23:59:59 Биолог. калейдоскоп 26.09 11 2022-09-28 13:37:00 11 192 249 5.0
2 530923 6 2 1 7 Биологический калейдоскоп 2022-09-06 18:39:22 2022-09-26 23:59:59 Биолог. калейдоскоп 26.09 7 2022-09-30 12:50:13 11 74 249 100.0
3 551221 5 1 1 7 Биологический калейдоскоп 2022-09-07 15:08:03 2022-09-26 23:59:59 Биолог. калейдоскоп 26.09 4 2022-09-28 11:56:59 11 69 249 5.0
4 575428 6 2 1 7 Биологический калейдоскоп 2022-09-07 19:47:42 2022-09-26 23:59:59 Биолог. калейдоскоп 26.09 7 2022-10-11 16:20:57 11 78 249 100.0
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 2458 entries, 0 to 2457
Data columns (total 15 columns):
 #   Column             Non-Null Count  Dtype         
---  ------             --------------  -----         
 0   lo_id              2458 non-null   int64         
 1   grade              2458 non-null   int64         
 2   study_year_id      2458 non-null   int64         
 3   school_app         2458 non-null   int64         
 4   course_id          2458 non-null   int64         
 5   course_name        2458 non-null   object        
 6   created_at         2458 non-null   datetime64[ns]
 7   accepted_at        2458 non-null   datetime64[ns]
 8   group_name         2458 non-null   object        
 9   lessons_completed  2458 non-null   int64         
 10  course_start_at    1521 non-null   datetime64[ns]
 11  course_lesson_cnt  2458 non-null   int64         
 12  student_score      2458 non-null   int64         
 13  max_score          2458 non-null   int64         
 14  test_result_final  2389 non-null   float64       
dtypes: datetime64[ns](3), float64(1), int64(9), object(2)
memory usage: 288.2+ KB

Упорядочем названия групп

In [ ]:
df.group_name = df.group_name.str[-5:]
df.group_name = pd.Categorical(df.group_name, ordered=True, categories=['26.09', '24.10', '28.11', '16.01'])
df.group_name
Out[ ]:
0       26.09
1       26.09
2       26.09
3       26.09
4       26.09
        ...  
2453    28.11
2454    16.01
2455    24.10
2456    16.01
2457    28.11
Name: group_name, Length: 2458, dtype: category
Categories (4, object): ['26.09' < '24.10' < '28.11' < '16.01']

Проверим дубли

In [ ]:
app_per_user_count = df.groupby(['lo_id', 'course_name'])['lo_id'].count()
bad_users = app_per_user_count[app_per_user_count > 1].index.droplevel(1)
display(len(bad_users))
bad_df = df[df.lo_id.isin(bad_users)].sort_values(by=['lo_id', 'course_name'])
bad_df
102
Out[ ]:
lo_id grade study_year_id school_app course_id course_name created_at accepted_at group_name lessons_completed course_start_at course_lesson_cnt student_score max_score test_result_final
430 402326 6 2 0 7 Биологический калейдоскоп 2022-10-01 21:48:23 2023-01-16 12:51:15 24.10 0 NaT 11 0 249 80.0
675 402326 6 2 0 7 Биологический калейдоскоп 2022-10-01 21:48:23 2023-01-16 12:51:15 16.01 0 NaT 11 0 249 80.0
333 444536 8 4 1 63 Основы программирования на языке Python 2022-11-23 21:14:28 2023-01-16 12:55:13 28.11 0 NaT 9 0 31 84.0
1334 444536 8 4 1 63 Основы программирования на языке Python 2022-11-23 21:14:28 2023-01-16 12:55:13 16.01 0 NaT 9 0 31 84.0
210 451851 8 4 1 63 Основы программирования на языке Python 2022-11-19 11:42:09 2023-01-16 12:55:13 16.01 0 NaT 9 0 31 78.0
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
2014 633796 5 1 0 8 Из чего сделано все вокруг? Начинаем изучать х... 2022-11-15 21:20:54 2022-11-28 14:36:35 28.11 1 2022-11-28 21:09:36 9 4 53 95.0
1937 633796 5 1 0 12 Математика для начинающих 2022-11-15 21:12:57 2022-11-28 13:55:46 28.11 1 2022-11-28 16:27:22 12 14 212 100.0
856 633796 5 1 0 14 Математическая физика для начинающих 2022-11-15 21:23:54 2022-11-28 14:27:57 28.11 3 2022-11-28 16:21:26 12 68 295 100.0
2066 633796 5 1 0 4 Подготовка к олимпиаде «Русский медвежонок» 2022-11-27 23:40:16 2023-01-16 12:57:14 28.11 0 NaT 7 0 56 100.0
2410 633796 5 1 0 4 Подготовка к олимпиаде «Русский медвежонок» 2022-11-27 23:40:16 2023-01-16 12:57:14 16.01 0 NaT 7 0 56 100.0

306 rows × 15 columns

Видим какое-то кол-во учеников, которые попали сразу в несколько групп. Тут правильно учитывать лишь самый последний поток.

In [ ]:
df['accepted_at'] = df.groupby(['lo_id', 'course_name'])['accepted_at'].transform('max')

df = df.drop_duplicates(subset=['lo_id', 'course_name', 'accepted_at'])
app_per_user_count = df.groupby(['lo_id', 'course_name'])['lo_id'].count()
bad_users = app_per_user_count[app_per_user_count > 1].index.droplevel(1)
display(len(bad_users))
0

Добавим интересующие нас признаки:

In [ ]:
df['course_started'] = 0
df.loc[df['lessons_completed'] >= 1, 'course_started'] = 1

df['course_50'] = 0
df.loc[df['lessons_completed'] * 2 >= df['course_lesson_cnt'], 'course_50'] = 1

df['course_100'] = 0
df.loc[df['lessons_completed'] == df['course_lesson_cnt'], 'course_100'] = 1

df['percent_score'] = df['student_score'] / df['max_score']
df
Out[ ]:
lo_id grade study_year_id school_app course_id course_name created_at accepted_at group_name lessons_completed course_start_at course_lesson_cnt student_score max_score test_result_final course_started course_50 course_100 percent_score
0 523390 6 2 1 7 Биологический калейдоскоп 2022-09-08 13:52:34 2022-09-26 23:59:59 26.09 9 2022-10-16 18:54:41 11 142 249 100.0 1 1 0 0.570281
1 525181 6 2 1 7 Биологический калейдоскоп 2022-09-05 19:41:42 2022-09-26 23:59:59 26.09 11 2022-09-28 13:37:00 11 192 249 5.0 1 1 1 0.771084
2 530923 6 2 1 7 Биологический калейдоскоп 2022-09-06 18:39:22 2022-09-26 23:59:59 26.09 7 2022-09-30 12:50:13 11 74 249 100.0 1 1 0 0.297189
3 551221 5 1 1 7 Биологический калейдоскоп 2022-09-07 15:08:03 2022-09-26 23:59:59 26.09 4 2022-09-28 11:56:59 11 69 249 5.0 1 0 0 0.277108
4 575428 6 2 1 7 Биологический калейдоскоп 2022-09-07 19:47:42 2022-09-26 23:59:59 26.09 7 2022-10-11 16:20:57 11 78 249 100.0 1 1 0 0.313253
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
2452 609462 5 1 1 12 Математика для начинающих 2022-09-09 18:52:06 2022-11-28 13:55:47 28.11 1 2022-12-06 06:45:28 12 4 212 75.0 1 0 0 0.018868
2453 551162 6 2 0 63 Основы программирования на языке Python 2022-11-26 18:56:00 2022-11-28 14:10:31 28.11 0 NaT 9 0 31 62.0 0 0 0 0.000000
2454 526417 5 1 1 7 Биологический калейдоскоп 2023-01-05 17:23:37 2023-01-16 12:51:15 16.01 10 2023-01-16 18:35:07 11 128 249 70.0 1 1 0 0.514056
2456 527409 7 3 1 77 Экономика фирмы 2023-01-08 16:35:49 2023-01-16 12:58:51 16.01 3 2023-01-25 18:01:23 11 10 39 100.0 1 0 0 0.256410
2457 611939 6 2 0 14 Математическая физика для начинающих 2022-09-25 19:28:34 2022-11-28 14:27:57 28.11 0 NaT 12 0 295 62.0 0 0 0 0.000000

2350 rows × 19 columns

Определим только начавших, а также тех, кто подал заявки

In [ ]:
df_start = df[df.course_started == 1].copy()
df_school = df_start[df_start.school_app == 1].copy()

Courses and groups info¶

Тут нам также понадобятся заявки (не только одобренные). Данные о группе были проставлены при SQL выгрузке по данным о дате подачи заявки

In [ ]:
course_apps = pd.read_csv('students_course_apps_groups.csv')
course_apps['group_name'] = course_apps['group_name'].astype(str)
course_apps.loc[course_apps['group_name'] == '24.1', 'group_name'] = '24.10'
course_apps.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 4179 entries, 0 to 4178
Data columns (total 3 columns):
 #   Column       Non-Null Count  Dtype 
---  ------       --------------  ----- 
 0   lo_id        4179 non-null   int64 
 1   course_name  4179 non-null   object
 2   group_name   4179 non-null   object
dtypes: int64(1), object(2)
memory usage: 98.1+ KB

Также добавим порядок

In [ ]:
course_apps['group_name'].unique()
Out[ ]:
array(['26.09', '24.10', '28.11', '16.01'], dtype=object)
In [ ]:
course_apps['group_name'] = pd.Categorical(course_apps['group_name'], ordered=True, categories=['26.09', '24.10', '28.11', '16.01'])
course_apps['group_name']
Out[ ]:
0       26.09
1       26.09
2       26.09
3       26.09
4       26.09
        ...  
4174    16.01
4175    16.01
4176    16.01
4177    16.01
4178    16.01
Name: group_name, Length: 4179, dtype: category
Categories (4, object): ['26.09' < '24.10' < '28.11' < '16.01']
In [ ]:
course_apps['group_name'].unique()
Out[ ]:
['26.09', '24.10', '28.11', '16.01']
Categories (4, object): ['26.09' < '24.10' < '28.11' < '16.01']
In [ ]:
course_apps_count = course_apps.groupby(['course_name', 'group_name']).agg({'lo_id':'count'})
course_apps_count.columns = ['Заявки']
course_apps_count
Out[ ]:
Заявки
course_name group_name
Академическое письмо (Academic Writing) 26.09 231
24.10 52
28.11 49
16.01 34
Античная философия 26.09 0
24.10 17
28.11 11
16.01 6
Биологический калейдоскоп 26.09 287
24.10 88
28.11 58
16.01 42
Из чего сделано все вокруг? Начинаем изучать химию 26.09 65
24.10 80
28.11 43
16.01 32
Лингвистика для начинающих 26.09 146
24.10 37
28.11 28
16.01 0
Математика для начинающих 26.09 415
24.10 117
28.11 83
16.01 0
Математическая физика для начинающих 26.09 64
24.10 79
28.11 63
16.01 53
Основы программирования на языке Python 26.09 571
24.10 165
28.11 89
16.01 86
Первые шаги в робототехнику 26.09 133
24.10 46
28.11 27
16.01 21
Подготовка к олимпиаде «Русский медвежонок» 26.09 356
24.10 99
28.11 60
16.01 50
Семейная экономика 26.09 52
24.10 52
28.11 37
16.01 27
Финансовая грамотность 26.09 0
24.10 41
28.11 33
16.01 0
Экономика фирмы 26.09 0
24.10 24
28.11 14
16.01 16
In [ ]:
course_info = df.groupby(['course_name', 'group_name']).agg({
     'test_result_final':'mean',
    'lo_id':'count', 
    'course_started':'sum', 
    'course_50':'sum', 'course_100':'sum'
    })
course_info.columns = ['Тест (%)', 'Одобренные заявки', 'Приступили', '50%', '100%']
course_info
Out[ ]:
Тест (%) Одобренные заявки Приступили 50% 100%
course_name group_name
Академическое письмо (Academic Writing) 26.09 75.042254 82 72 40 7
24.10 74.431373 51 14 8 3
28.11 77.020833 49 11 3 1
16.01 81.129032 32 12 4 1
Античная философия 26.09 NaN 0 0 0 0
24.10 NaN 0 0 0 0
28.11 72.727273 11 5 4 0
16.01 66.500000 4 3 2 0
Биологический калейдоскоп 26.09 78.134328 144 140 79 14
24.10 82.741935 63 47 26 8
28.11 86.666667 54 38 19 6
16.01 72.432432 37 26 14 0
Из чего сделано все вокруг? Начинаем изучать химию 26.09 NaN 0 0 0 0
24.10 79.955357 114 71 37 9
28.11 86.578947 38 19 3 1
16.01 75.833333 24 17 10 2
Лингвистика для начинающих 26.09 93.347826 23 23 13 2
24.10 NaN 0 0 0 0
28.11 89.360656 62 17 6 0
16.01 NaN 0 0 0 0
Математика для начинающих 26.09 71.040000 75 74 57 14
24.10 NaN 0 0 0 0
28.11 80.527778 181 84 51 11
16.01 NaN 0 0 0 0
Математическая физика для начинающих 26.09 NaN 0 0 0 0
24.10 73.047619 70 38 19 3
28.11 74.272727 100 45 24 1
16.01 77.475000 40 30 16 0
Основы программирования на языке Python 26.09 75.988701 184 176 104 33
24.10 70.933333 108 68 34 9
28.11 77.301075 94 48 27 8
16.01 78.883117 78 40 27 10
Первые шаги в робототехнику 26.09 67.731707 42 41 26 9
24.10 70.906250 32 25 14 5
28.11 68.548387 32 19 8 3
16.01 79.437500 16 9 5 0
Подготовка к олимпиаде «Русский медвежонок» 26.09 82.925373 135 133 80 12
24.10 81.530303 68 31 19 2
28.11 83.766667 62 25 12 3
16.01 84.416667 36 21 9 1
Семейная экономика 26.09 NaN 0 0 0 0
24.10 72.773333 75 45 25 3
28.11 77.371429 35 18 9 2
16.01 79.909091 22 14 7 0
Финансовая грамотность 26.09 NaN 0 0 0 0
24.10 NaN 0 0 0 0
28.11 77.861111 36 19 9 2
16.01 NaN 0 0 0 0
Экономика фирмы 26.09 NaN 0 0 0 0
24.10 NaN 0 0 0 0
28.11 85.884615 27 11 2 1
16.01 83.357143 14 4 1 0
In [ ]:
course_start_info = df_start.groupby(['course_name', 'group_name']).agg({
    'school_app':'sum', 'percent_score':'mean'
})
course_start_info.columns = ['Заявки в школу', 'Средний результат (%)']
course_start_info
Out[ ]:
Заявки в школу Средний результат (%)
course_name group_name
Академическое письмо (Academic Writing) 26.09 54 0.342907
24.10 8 0.319695
28.11 9 0.215357
16.01 10 0.287217
Античная философия 26.09 0 NaN
24.10 0 NaN
28.11 2 0.432727
16.01 3 0.375758
Биологический калейдоскоп 26.09 44 0.258090
24.10 12 0.241306
28.11 11 0.224900
16.01 10 0.255638
Из чего сделано все вокруг? Начинаем изучать химию 26.09 0 NaN
24.10 20 0.282487
28.11 8 0.163853
16.01 4 0.306326
Лингвистика для начинающих 26.09 15 0.272791
24.10 0 NaN
28.11 10 0.227704
16.01 0 NaN
Математика для начинающих 26.09 26 0.466790
24.10 0 NaN
28.11 30 0.411107
16.01 0 NaN
Математическая физика для начинающих 26.09 0 NaN
24.10 14 0.289384
28.11 18 0.280678
16.01 10 0.302260
Основы программирования на языке Python 26.09 74 0.443548
24.10 27 0.419355
28.11 22 0.457661
16.01 14 0.533065
Первые шаги в робототехнику 26.09 12 0.398999
24.10 11 0.424615
28.11 7 0.302294
16.01 2 0.339031
Подготовка к олимпиаде «Русский медвежонок» 26.09 52 0.458915
24.10 12 0.379608
28.11 13 0.386429
16.01 10 0.333333
Семейная экономика 26.09 0 NaN
24.10 20 0.344668
28.11 6 0.329763
16.01 6 0.337923
Финансовая грамотность 26.09 0 NaN
24.10 0 NaN
28.11 13 0.353011
16.01 0 NaN
Экономика фирмы 26.09 0 NaN
24.10 0 NaN
28.11 7 0.156177
16.01 4 0.269231
In [ ]:
course_school_result = df_school.groupby(['course_name', 'group_name']).agg({'percent_score':'mean'})
course_school_result.columns = ['Результат абитуриентов (%)']
course_school_result
Out[ ]:
Результат абитуриентов (%)
course_name group_name
Академическое письмо (Academic Writing) 26.09 0.335671
24.10 0.308252
28.11 0.238403
16.01 0.242718
Античная философия 26.09 NaN
24.10 NaN
28.11 0.531818
16.01 0.375758
Биологический калейдоскоп 26.09 0.296459
24.10 0.205489
28.11 0.235487
16.01 0.239759
Из чего сделано все вокруг? Начинаем изучать химию 26.09 NaN
24.10 0.224528
28.11 0.134434
16.01 0.146226
Лингвистика для начинающих 26.09 0.207527
24.10 NaN
28.11 0.229032
16.01 NaN
Математика для начинающих 26.09 0.447025
24.10 NaN
28.11 0.336006
16.01 NaN
Математическая физика для начинающих 26.09 NaN
24.10 0.182082
28.11 0.251224
16.01 0.226102
Основы программирования на языке Python 26.09 0.434612
24.10 0.344086
28.11 0.368035
16.01 0.543779
Первые шаги в робототехнику 26.09 0.395299
24.10 0.384615
28.11 0.227106
16.01 0.384615
Подготовка к олимпиаде «Русский медвежонок» 26.09 0.466690
24.10 0.394345
28.11 0.251374
16.01 0.325000
Семейная экономика 26.09 NaN
24.10 0.345582
28.11 0.265730
16.01 0.186747
Финансовая грамотность 26.09 NaN
24.10 NaN
28.11 0.286902
16.01 NaN
Экономика фирмы 26.09 NaN
24.10 NaN
28.11 0.124542
16.01 0.269231
In [ ]:
course_info_fin = course_apps_count.join(course_info, how='left').join(course_start_info, how='left').join(course_school_result, how='left')

#Посчитаем реальное кол-во висящих заявок на дату одобрения:
course_info_fin.insert(2, 'Заявки на дату одобрения', course_info_fin.groupby(level=0).apply(
    lambda x: ((x['Заявки'] - x['Одобренные заявки']).shift(1).cumsum() + x['Заявки']).fillna(x['Заявки'])
    ).droplevel(0)
)
course_info_fin.insert(4, 'Одобренные (%)', course_info_fin['Одобренные заявки'] / course_info_fin['Заявки на дату одобрения'])
course_info_fin.insert(6, 'Приступили (%)', course_info_fin['Приступили'] / course_info_fin['Одобренные заявки'])
course_info_fin.insert(10, 'Заявки в школу (% от приступивших)', course_info_fin['Заявки в школу'] / course_info_fin['Приступили'])

course_info_fin = course_info_fin[course_info_fin['Одобренные заявки'] != 0]
#course_info_fin.to_excel('courses_groups_summary.xlsx')
course_info_fin
Out[ ]:
Заявки Тест (%) Заявки на дату одобрения Одобренные заявки Одобренные (%) Приступили Приступили (%) 50% 100% Заявки в школу Заявки в школу (% от приступивших) Средний результат (%) Результат абитуриентов (%)
course_name group_name
Академическое письмо (Academic Writing) 26.09 231 75.042254 231.0 82 0.354978 72 0.878049 40 7 54 0.750000 0.342907 0.335671
24.10 52 74.431373 201.0 51 0.253731 14 0.274510 8 3 8 0.571429 0.319695 0.308252
28.11 49 77.020833 199.0 49 0.246231 11 0.224490 3 1 9 0.818182 0.215357 0.238403
16.01 34 81.129032 184.0 32 0.173913 12 0.375000 4 1 10 0.833333 0.287217 0.242718
Античная философия 28.11 11 72.727273 28.0 11 0.392857 5 0.454545 4 0 2 0.400000 0.432727 0.531818
16.01 6 66.500000 23.0 4 0.173913 3 0.750000 2 0 3 1.000000 0.375758 0.375758
Биологический калейдоскоп 26.09 287 78.134328 287.0 144 0.501742 140 0.972222 79 14 44 0.314286 0.258090 0.296459
24.10 88 82.741935 231.0 63 0.272727 47 0.746032 26 8 12 0.255319 0.241306 0.205489
28.11 58 86.666667 226.0 54 0.238938 38 0.703704 19 6 11 0.289474 0.224900 0.235487
16.01 42 72.432432 214.0 37 0.172897 26 0.702703 14 0 10 0.384615 0.255638 0.239759
Из чего сделано все вокруг? Начинаем изучать химию 24.10 80 79.955357 145.0 114 0.786207 71 0.622807 37 9 20 0.281690 0.282487 0.224528
28.11 43 86.578947 74.0 38 0.513514 19 0.500000 3 1 8 0.421053 0.163853 0.134434
16.01 32 75.833333 68.0 24 0.352941 17 0.708333 10 2 4 0.235294 0.306326 0.146226
Лингвистика для начинающих 26.09 146 93.347826 146.0 23 0.157534 23 1.000000 13 2 15 0.652174 0.272791 0.207527
28.11 28 89.360656 188.0 62 0.329787 17 0.274194 6 0 10 0.588235 0.227704 0.229032
Математика для начинающих 26.09 415 71.040000 415.0 75 0.180723 74 0.986667 57 14 26 0.351351 0.466790 0.447025
28.11 83 80.527778 540.0 181 0.335185 84 0.464088 51 11 30 0.357143 0.411107 0.336006
Математическая физика для начинающих 24.10 79 73.047619 143.0 70 0.489510 38 0.542857 19 3 14 0.368421 0.289384 0.182082
28.11 63 74.272727 136.0 100 0.735294 45 0.450000 24 1 18 0.400000 0.280678 0.251224
16.01 53 77.475000 89.0 40 0.449438 30 0.750000 16 0 10 0.333333 0.302260 0.226102
Основы программирования на языке Python 26.09 571 75.988701 571.0 184 0.322242 176 0.956522 104 33 74 0.420455 0.443548 0.434612
24.10 165 70.933333 552.0 108 0.195652 68 0.629630 34 9 27 0.397059 0.419355 0.344086
28.11 89 77.301075 533.0 94 0.176360 48 0.510638 27 8 22 0.458333 0.457661 0.368035
16.01 86 78.883117 525.0 78 0.148571 40 0.512821 27 10 14 0.350000 0.533065 0.543779
Первые шаги в робототехнику 26.09 133 67.731707 133.0 42 0.315789 41 0.976190 26 9 12 0.292683 0.398999 0.395299
24.10 46 70.906250 137.0 32 0.233577 25 0.781250 14 5 11 0.440000 0.424615 0.384615
28.11 27 68.548387 132.0 32 0.242424 19 0.593750 8 3 7 0.368421 0.302294 0.227106
16.01 21 79.437500 121.0 16 0.132231 9 0.562500 5 0 2 0.222222 0.339031 0.384615
Подготовка к олимпиаде «Русский медвежонок» 26.09 356 82.925373 356.0 135 0.379213 133 0.985185 80 12 52 0.390977 0.458915 0.466690
24.10 99 81.530303 320.0 68 0.212500 31 0.455882 19 2 12 0.387097 0.379608 0.394345
28.11 60 83.766667 312.0 62 0.198718 25 0.403226 12 3 13 0.520000 0.386429 0.251374
16.01 50 84.416667 300.0 36 0.120000 21 0.583333 9 1 10 0.476190 0.333333 0.325000
Семейная экономика 24.10 52 72.773333 104.0 75 0.721154 45 0.600000 25 3 20 0.444444 0.344668 0.345582
28.11 37 77.371429 66.0 35 0.530303 18 0.514286 9 2 6 0.333333 0.329763 0.265730
16.01 27 79.909091 58.0 22 0.379310 14 0.636364 7 0 6 0.428571 0.337923 0.186747
Финансовая грамотность 28.11 33 77.861111 74.0 36 0.486486 19 0.527778 9 2 13 0.684211 0.353011 0.286902
Экономика фирмы 28.11 14 85.884615 38.0 27 0.710526 11 0.407407 2 1 7 0.636364 0.156177 0.124542
16.01 16 83.357143 27.0 14 0.518519 4 0.285714 1 0 4 1.000000 0.269231 0.269231

EDA¶

Результаты курсов¶

Гистограммы¶

In [ ]:
course_group = course_info_fin.index.tolist()
course_group
Out[ ]:
[0,
 1,
 2,
 3,
 4,
 5,
 6,
 7,
 8,
 9,
 10,
 11,
 12,
 13,
 14,
 15,
 16,
 17,
 18,
 19,
 20,
 21,
 22,
 23,
 24,
 25,
 26,
 27,
 28,
 29,
 30,
 31,
 32,
 33,
 34,
 35,
 36,
 37]
In [ ]:
def histplot_hue_percent(data, x, hue, title=None, xlabel=None, ylabel=None, hue_legend_labels=None, hue_legend_title=None, palette=None, bins=10):    
    ax = sns.histplot(data=data, x=x, hue=hue, palette=palette, multiple='stack', bins=bins)
    ax.xaxis.set_major_formatter(ticker.PercentFormatter(1, decimals=0))
    percents = [f'{x.get_height() / (x.get_height() + y.get_height()):.1%}' if x.get_height()!=0 else '' for x, y  in zip(ax.containers[0], ax.containers[1])]
    ax.bar_label(ax.containers[1], percents, fontsize=18)
    
    plt.grid(axis='x')
    if title:
        ax.set_title(title)
    if xlabel:
        ax.set_xlabel(xlabel)
    if ylabel:
        ax.set_ylabel(ylabel)
    if hue_legend_labels:
        legend = ax.get_legend()
        handles = legend.legendHandles
        labels = hue_legend_labels
        ax.legend(handles, labels, title=hue_legend_title)
    return ax
histplot_hue_percent(data=df_start, x='percent_score', hue='school_app', palette=palette_dict,
                     title='Результаты (показан процент абитуриентов)', 
                     xlabel='Результат за курс (%)', ylabel='Кол-во',
                     hue_legend_labels=['Нет', 'Да'], hue_legend_title='Заявка в школу')
plt.show()
In [ ]:
for course, group in course_group:
    if course_info_fin.loc[(course, group), 'Приступили'] >= 30:
        data = df_start[(df_start.course_name == course) & (df_start.group_name == group)]
        if data.shape[0] != 0:
            histplot_hue_percent(data=data, x='percent_score', hue='school_app',
                                 title = f'Распределение результатов за курс (показан процент абитуриентов)\n{course} - {group}',
                                 xlabel='Результат (%)', ylabel='Кол-во учеников',
                                 palette=palette_dict,
                                 hue_legend_title='Заявка в школу', hue_legend_labels=['Нет', 'Да'])
            plt.show()

Бокс-плоты¶

In [ ]:
courses = df_start.course_name.unique()
In [ ]:
def continuous_by_groups_plot(df: pd.DataFrame, x: str, y: str, outliers=False,
                              title: str=None, xlabel: str=None, ylabel: str=None, palette='Set2',
                              hue: str=None, hue_legend_labels: str=None, hue_legend_title: str=None):

  ax = sns.boxplot(data=df, x=x, y=y, showfliers=outliers, hue=hue, palette=palette)
  if title:
    ax.set_title(title)
  if xlabel:
    ax.set_xlabel(xlabel)
  if ylabel:
    ax.set_ylabel(ylabel)
  if hue_legend_labels:
    handles, labels = ax.get_legend_handles_labels()
    labels = hue_legend_labels
    ax.legend(handles, labels, title=hue_legend_title)
  ax.yaxis.set_major_formatter(ticker.PercentFormatter(1, decimals=0))
  return ax

for course in courses:
    data = df_start[df_start.course_name == course]
    continuous_by_groups_plot(data, 'group_name', 'percent_score', 
                              title=f'Результаты по курсу {course}', hue='school_app', palette=palette_dict,
                              xlabel='Дата одобрения', ylabel=' ',
                              hue_legend_labels=['Нет', 'Да'], hue_legend_title='Заявка в школу',
                              outliers=True)
    plt.show()

Результаты тестов¶

В этом разделе посмотрим на результаты начального тестирования среди одобренных заявок, т.е. с каким результатом одобрялись ученики

In [ ]:
df_start.columns
Out[ ]:
Index(['lo_id', 'grade', 'study_year_id', 'school_app', 'course_id',
       'course_name', 'created_at', 'accepted_at', 'group_name',
       'lessons_completed', 'course_start_at', 'course_lesson_cnt',
       'student_score', 'max_score', 'test_result_final', 'course_started',
       'course_50', 'course_100', 'percent_score'],
      dtype='object')
In [ ]:
df_start['test_result_final'] = df_start['test_result_final'] / 100
In [ ]:
for course in courses:
    data = df_start[df_start.course_name == course]
    continuous_by_groups_plot(data, 'group_name', 'test_result_final', 
                              title=f'Результаты тестов перед одобрением\n{course}', hue='school_app', palette=palette_dict,
                              xlabel='Дата одобрения', ylabel=' ',
                              hue_legend_labels=['Нет', 'Да'], hue_legend_title='Заявка в школу',
                              outliers=True)
    plt.show()

Воронки¶

In [ ]:
course_info_fin = course_info_fin.reset_index()
In [ ]:
def make_funnel_group(df, fig, name): 

    user_apps = df['Заявки на дату одобрения'].iloc[0]

    user_approve = df['Одобренные заявки'].iloc[0]

    user_start = df['Приступили'].iloc[0]

    names = ['Заявки', 'Одобрены', 'Приступили'] #'50%', '100%']

    ls_unique = [user_apps, user_approve, user_start]#, user_50, user_100]
    fig.add_trace(go.Funnel(
        name = name,
        y = names,
        x = ls_unique,
        orientation='h',
        textinfo = "value+percent previous",
        textfont={'size':16}
    )
    )
    

    return ls_unique
In [ ]:
course_name = 'Математика для начинающих'

for course in course_info_fin.course_name.unique():
    data = course_info_fin[course_info_fin.course_name == course]

    fig = go.Figure()
    for group in data.group_name.unique():    
        make_funnel_group(data[data.group_name == group], fig, group)
    fig.update_layout(title=course)
    fig.show()